{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true,
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "### 生成式\n",
    "通过列表生成式，我们可以直接创建一个列表。但是，受到内存限制，列表容量肯定是有限的。而且，创建一个包含100万个元素的列表，不仅占用很大的存储空间，如果我们仅仅需要访问前面几个元素，那后面绝大多数元素占用的空间都白白浪费了。\n",
    "\n",
    "所以，如果列表元素可以按照某种算法推算出来，那我们是否可以在循环的过程中不断推算出后续的元素呢？这样就不必创建完整的list，从而节省大量的空间。在Python中，这种一边循环一边计算的机制，称为生成器：generator。\n",
    "\n",
    "要创建一个generator，有很多种方法。第一种方法很简单，只要把一个列表生成式的`[]`改成`()`，就创建了一个generator：\n",
    "\n",
    "```\n",
    ">>> L = [x * x for x in range(10)]\n",
    ">>> L\n",
    "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n",
    ">>> g = (x * x for x in range(10))\n",
    ">>> g\n",
    "<generator object <genexpr> at 0x1022ef630>\n",
    "```\n",
    "\n",
    "创建`L`和`g`的区别仅在于最外层的`[]`和`()`，`L`是一个list，而`g`是一个generator。\n",
    "\n",
    "我们可以直接打印出list的每一个元素，但我们怎么打印出generator的每一个元素呢？\n",
    "\n",
    "如果要一个一个打印出来，可以通过`next()`函数获得generator的下一个返回值：\n",
    "\n",
    "```\n",
    ">>> next(g)\n",
    "0\n",
    ">>> next(g)\n",
    "1\n",
    ">>> next(g)\n",
    "4\n",
    ">>> next(g)\n",
    "9\n",
    ">>> next(g)\n",
    "16\n",
    ">>> next(g)\n",
    "25\n",
    ">>> next(g)\n",
    "36\n",
    ">>> next(g)\n",
    "49\n",
    ">>> next(g)\n",
    "64\n",
    ">>> next(g)\n",
    "81\n",
    ">>> next(g)\n",
    "Traceback (most recent call last):\n",
    "  File \"<stdin>\", line 1, in <module>\n",
    "StopIteration\n",
    "```\n",
    "\n",
    "我们讲过，generator保存的是算法，每次调用`next(g)`，就计算出`g`的下一个元素的值，直到计算到最后一个元素，没有更多的元素时，抛出`StopIteration`的错误。\n",
    "\n",
    "当然，上面这种不断调用`next(g)`实在是太变态了，正确的方法是使用`for`循环，因为generator也是可迭代对象：\n",
    "\n",
    "```\n",
    ">>> g = (x * x for x in range(10))\n",
    ">>> for n in g:\n",
    "...     print(n)\n",
    "...\n",
    "0\n",
    "1\n",
    "4\n",
    "9\n",
    "16\n",
    "25\n",
    "36\n",
    "49\n",
    "64\n",
    "81\n",
    "```\n",
    "\n",
    "所以，我们创建了一个generator后，基本上永远不会调用`next()`，而是通过`for`循环来迭代它，并且不需要关心`StopIteration`的错误。\n",
    "\n",
    "generator非常强大。如果推算的算法比较复杂，用类似列表生成式的`for`循环无法实现的时候，还可以用函数来实现。\n",
    "\n",
    "比如，著名的斐波拉契数列（Fibonacci），除第一个和第二个数外，任意一个数都可由前两个数相加得到：\n",
    "\n",
    "1, 1, 2, 3, 5, 8, 13, 21, 34, ...\n",
    "\n",
    "斐波拉契数列用列表生成式写不出来，但是，用函数把它打印出来却很容易：\n",
    "\n",
    "```\n",
    "def fib(max):\n",
    "    n, a, b = 0, 0, 1\n",
    "    while n < max:\n",
    "        print(b)\n",
    "        a, b = b, a + b\n",
    "        n = n + 1\n",
    "    return 'done'\n",
    "```\n",
    "\n",
    "_注意_，赋值语句：\n",
    "\n",
    "```\n",
    "a, b = b, a + b\n",
    "```\n",
    "\n",
    "相当于：\n",
    "\n",
    "```\n",
    "t = (b, a + b) # t是一个tuple\n",
    "a = t[0]\n",
    "b = t[1]\n",
    "```\n",
    "\n",
    "但不必显式写出临时变量t就可以赋值。\n",
    "\n",
    "上面的函数可以输出斐波那契数列的前N个数：\n",
    "\n",
    "```\n",
    ">>> fib(6)\n",
    "1\n",
    "1\n",
    "2\n",
    "3\n",
    "5\n",
    "8\n",
    "'done'\n",
    "```\n",
    "\n",
    "仔细观察，可以看出，`fib`函数实际上是定义了斐波拉契数列的推算规则，可以从第一个元素开始，推算出后续任意的元素，这种逻辑其实非常类似generator。\n",
    "\n",
    "也就是说，上面的函数和generator仅一步之遥。要把`fib`函数变成generator函数，只需要把`print(b)`改为`yield b`就可以了：\n",
    "\n",
    "```\n",
    "def fib(max):\n",
    "    n, a, b = 0, 0, 1\n",
    "    while n < max:\n",
    "        yield b\n",
    "        a, b = b, a + b\n",
    "        n = n + 1\n",
    "    return 'done'\n",
    "```\n",
    "\n",
    "这就是定义generator的另一种方法。如果一个函数定义中包含`yield`关键字，那么这个函数就不再是一个普通函数，而是一个generator函数，调用一个generator函数将返回一个generator：\n",
    "\n",
    "```\n",
    ">>> f = fib(6)\n",
    ">>> f\n",
    "<generator object fib at 0x104feaaa0>\n",
    "```\n",
    "\n",
    "这里，最难理解的就是generator函数和普通函数的执行流程不一样。普通函数是顺序执行，遇到`return`语句或者最后一行函数语句就返回。而变成generator的函数，在每次调用`next()`的时候执行，遇到`yield`语句返回，再次执行时从上次返回的`yield`语句处继续执行。\n",
    "\n",
    "举个简单的例子，定义一个generator函数，依次返回数字1，3，5：\n",
    "\n",
    "```\n",
    "def odd():\n",
    "    print('step 1')\n",
    "    yield 1\n",
    "    print('step 2')\n",
    "    yield(3)\n",
    "    print('step 3')\n",
    "    yield(5)\n",
    "```\n",
    "\n",
    "调用该generator函数时，首先要生成一个generator对象，然后用`next()`函数不断获得下一个返回值：\n",
    "\n",
    "```\n",
    ">>> o = odd()\n",
    ">>> next(o)\n",
    "step 1\n",
    "1\n",
    ">>> next(o)\n",
    "step 2\n",
    "3\n",
    ">>> next(o)\n",
    "step 3\n",
    "5\n",
    ">>> next(o)\n",
    "Traceback (most recent call last):\n",
    "  File \"<stdin>\", line 1, in <module>\n",
    "StopIteration\n",
    "```\n",
    "\n",
    "可以看到，`odd`不是普通函数，而是generator函数，在执行过程中，遇到`yield`就中断，下次又继续执行。执行3次`yield`后，已经没有`yield`可以执行了，所以，第4次调用`next(o)`就报错。\n",
    "\n",
    "请务必注意：调用generator函数会创建一个generator对象，多次调用generator函数会创建多个相互独立的generator。\n",
    "\n",
    "有的童鞋会发现这样调用`next()`每次都返回1：\n",
    "\n",
    "```\n",
    ">>> next(odd())\n",
    "step 1\n",
    "1\n",
    ">>> next(odd())\n",
    "step 1\n",
    "1\n",
    ">>> next(odd())\n",
    "step 1\n",
    "1\n",
    "```\n",
    "\n",
    "原因在于`odd()`会创建一个新的generator对象，上述代码实际上创建了3个完全独立的generator，对3个generator分别调用`next()`当然每个都会返回第一个值。\n",
    "\n",
    "正确的写法是创建一个generator对象，然后不断对这一个generator对象调用`next()`：\n",
    "\n",
    "```\n",
    ">>> g = odd()\n",
    ">>> next(g)\n",
    "step 1\n",
    "1\n",
    ">>> next(g)\n",
    "step 2\n",
    "3\n",
    ">>> next(g)\n",
    "step 3\n",
    "5\n",
    "```\n",
    "\n",
    "回到`fib`的例子，我们在循环过程中不断调用`yield`，就会不断中断。当然要给循环设置一个条件来退出循环，不然就会产生一个无限数列出来。\n",
    "\n",
    "同样的，把函数改成generator函数后，我们基本上从来不会用`next()`来获取下一个返回值，而是直接使用`for`循环来迭代：\n",
    "\n",
    "```\n",
    ">>> for n in fib(6):\n",
    "...     print(n)\n",
    "...\n",
    "1\n",
    "1\n",
    "2\n",
    "3\n",
    "5\n",
    "8\n",
    "```\n",
    "\n",
    "但是用`for`循环调用generator时，发现拿不到generator的`return`语句的返回值。如果想要拿到返回值，必须捕获`StopIteration`错误，返回值包含在`StopIteration`的`value`中：\n",
    "\n",
    "```\n",
    ">>> g = fib(6)\n",
    ">>> while True:\n",
    "...     try:\n",
    "...         x = next(g)\n",
    "...         print('g:', x)\n",
    "...     except StopIteration as e:\n",
    "...         print('Generator return value:', e.value)\n",
    "...         break\n",
    "...\n",
    "g: 1\n",
    "g: 1\n",
    "g: 2\n",
    "g: 3\n",
    "g: 5\n",
    "g: 8\n",
    "Generator return value: done\n",
    "```\n",
    "\n",
    "关于如何捕获错误，后面的错误处理还会详细讲解。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}